洛基走進茶室還在想著昨天大師提到「重新思考資料組織方式」。
洛基坐下後說道,「昨天我學會了 Query 的基本操作,能夠查詢火星基地在特定時間範圍的活動。但我在想,如果我需要支援更多種查詢方式怎麼辦?」
諾斯克大師微笑:「很好的問題!這就是今天要解決的挑戰。」他在白板上寫下一個現實場景:
星際活動系統的新需求:
原有:查詢火星基地在特定時間的活動 ✓ (昨天已解決)
新增:
1. 按主題查詢活動 (例如:所有 Foundation 主題的活動)
2. 按星球查詢活動 (例如:火星的所有活動,不限時間)
「讓我們從簡單的開始,」大師說,「用你昨天學到的技能,先試試能否解決這兩個新需求。」
洛基回想起 Day05 學到的方案:
{
"PK": "LOCATION#MARS",
"SK": "DATE#SY210-03-15#EVENT#001",
"name": "火星防禦研討會",
"topic": "Foundation",
"speaker": "Asimov",
"capacity": 500
}
「這個設計能夠很好地支援『查詢火星基地特定時間範圍的活動』,」洛基分析,「但如果我要查詢所有 Foundation 主題的活動呢?」
洛基嘗試查詢所有 Foundation 主題的活動:
# 嘗試按主題查詢
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "topic = :topic" \
--expression-attribute-values '{":topic": {"S": "Foundation"}}' \
--endpoint-url http://localhost:8000
結果報錯:Query condition missed key schema element
洛基困惑:「為什麼不行?topic 明明就在資料裡啊!」
「因為 topic 不是 Key,」大師解釋,「Query 只能基於 Key (PK 和 SK) 來查詢。其他欄位只能用 Scan + Filter 的方式。」
洛基嘗試 Scan:
# 用 Scan 查詢主題
aws dynamodb scan \
--table-name IntergalacticEvents \
--filter-expression "topic = :topic" \
--expression-attribute-values '{":topic": {"S": "Foundation"}}' \
--endpoint-url http://localhost:8000
「能找到結果,但是...」洛基看著 ConsumedCapacity,「這個成本好高!它掃描了整個表格!」
# 查詢火星的所有活動
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "PK = :pk" \
--expression-attribute-values '{":pk": {"S": "LOCATION#MARS"}}' \
--endpoint-url http://localhost:8000
「這個倒是可以!」洛基鬆了一口氣,「因為 PK 就是 LOCATION#MARS。」
洛基整理他的發現:
當前設計的查詢能力:
✓ 按星球查詢活動 → Query (高效)
✓ 按星球+時間範圍查詢 → Query (高效)
❌ 按主題查詢活動 → 只能 Scan (低效)
❌ 按講者查詢活動 → 只能 Scan (低效)
❌ 按日期查詢活動 → 只能 Scan (低效)
「我明白了!」洛基恍然大悟,「在 DynamoDB 中,只有設計成 Key 的欄位才能高效查詢。如果我想要高效地按主題查詢,就必須讓主題也變成某個 Key。」
大師點頭:「正確的理解!那你覺得應該怎麼解決?」
洛基思考了一會兒:「如果我想要高效地按主題查詢...我需要一個以主題為 PK 的『視角』?」
「很好的想法!讓我示範給你看。」大師在白板上畫出新的設計:
設計思維轉換:一個實體,多個視角
同一個活動,可以從不同角度儲存:
視角1:按星球查詢
{
"PK": "PLANET#MARS",
"SK": "EVENT#001",
"name": "火星防禦研討會",
"topic": "Foundation",
"date": "SY210-03-15"
}
視角2:按主題查詢
{
"PK": "TOPIC#Foundation",
"SK": "EVENT#001",
"name": "火星防禦研討會",
"planet": "MARS",
"date": "SY210-03-15"
}
洛基驚訝:「同一個活動...儲存兩次?在傳統資料庫中這是重複資料啊!」
「這就是 NoSQL 思維的重要轉換,」大師說,「我們用『空間換時間』。重複儲存是為了獲得查詢效能。」
讓我們建立測試資料:
# 視角1:按星球查詢的資料
aws dynamodb put-item \
--table-name IntergalacticEvents \
--item '{
"PK": {"S": "PLANET#MARS"},
"SK": {"S": "EVENT#001"},
"name": {"S": "火星防禦研討會"},
"topic": {"S": "Foundation"},
"date": {"S": "SY210-03-15"},
"speaker": {"S": "Asimov"}
}' \
--endpoint-url http://localhost:8000
# 視角2:按主題查詢的資料
aws dynamodb put-item \
--table-name IntergalacticEvents \
--item '{
"PK": {"S": "TOPIC#Foundation"},
"SK": {"S": "EVENT#001"},
"name": {"S": "火星防禦研討會"},
"planet": {"S": "MARS"},
"date": {"S": "SY210-03-15"},
"speaker": {"S": "Asimov"}
}' \
--endpoint-url http://localhost:8000
現在測試查詢:
# 按星球查詢 (使用視角1)
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "PK = :pk" \
--expression-attribute-values '{":pk": {"S": "PLANET#MARS"}}' \
--endpoint-url http://localhost:8000
# 按主題查詢 (使用視角2)
aws dynamodb query \
--table-name IntergalacticEvents \
--key-condition-expression "PK = :pk" \
--expression-attribute-values '{":pk": {"S": "TOPIC#Foundation"}}' \
--endpoint-url http://localhost:8000
洛基執行後驚喜地說:「兩個查詢都能高效執行!而且都是 Query 操作,不是 Scan!」
Hippo 跳出來補充:「讓我用圖表幫你理解這個概念!」
傳統關聯式思維:
Events Table → 一個活動只存一次
查詢不同維度 → 需要複雜的索引或 JOIN
DynamoDB 多視角思維:
同一個活動 → 按照不同查詢需求,建立不同的「視角」
每個視角 → 都是為特定查詢優化的資料組織方式
視角1: PLANET# → 支援按星球查詢
視角2: TOPIC# → 支援按主題查詢
視角3: DATE# → 支援按日期查詢 (如果需要的話)
洛基點頭:「所以每個視角就像是為特定查詢需求量身訂做的『快速通道』!」
「完美的比喻!」大師讚許,「每個視角都讓某種查詢變得極其高效。」
大師在白板上寫下重要原則:
多視角設計的基本原則:
1. 識別查詢需求
- 用戶會如何使用這個系統?
- 哪些查詢是最常見的?
2. 為重要查詢建立視角
- 每個常用查詢都應該有對應的視角
- 用不同的 PK 支援不同的查詢維度
3. 接受資料重複
- 同一個實體在不同視角中重複儲存
- 這是為了查詢效能的合理代價
4. 保持資料一致性
- 更新時需要同時更新所有相關視角
- 這是多視角設計的維護考量
洛基認真記錄:「所以設計 DynamoDB 表格時,我應該先問:『用戶會如何查詢這些資料?』」
「正確!」大師說,「這就是 NoSQL 設計思維的核心轉換。」
大師接著補充:「今天你學會了多視角設計的基本概念。明天,我們會看看當查詢需求繼續增加時會遇到什麼新的挑戰。」
「讓我分享一些實用的設計技巧!」Hippo 說。
// 視角設計的命名規範
const viewNamingPatterns = {
byPlanet: "PLANET#{planetName}", // 按星球查詢
byTopic: "TOPIC#{topicName}", // 按主題查詢
byDate: "DATE#{date}", // 按日期查詢
bySpeaker: "SPEAKER#{speakerName}", // 按講者查詢
byStatus: "STATUS#{status}" // 按狀態查詢
};
// 實體重複的策略
const entityDuplication = {
fullCopy: "完整複製所有欄位到每個視角",
partialCopy: "每個視角只包含該查詢需要的欄位",
referencePattern: "視角只包含 ID,詳細資料另外查詢"
};
# 模式1:基本多視角 (2-3個查詢維度)
# 適用:簡單的查詢需求
# 模式2:分層視角 (時間 + 分類)
PK: "TOPIC#Foundation"
SK: "DATE#SY210-03-15#EVENT#001"
# 既能按主題查詢,又能按主題+時間範圍查詢
# 模式3:複合視角 (多個維度組合)
PK: "PLANET#MARS#TOPIC#Foundation"
SK: "EVENT#001"
# 支援特定星球+特定主題的組合查詢
何時使用多視角設計?
✓ 查詢模式明確且固定
✓ 查詢效能要求高
✓ 可以接受一些維護複雜度